package org.terot.framework.fuzzy.instance;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.Collection;

import org.terot.util.Logg;

final class FactoryMethodInstantiator {
	
	/**
	 * 
	 * @param <T>
	 * @param cl
	 * @return
	 */
	protected static<T> Collection<T> generateInstances(Class<T> cl) {

		ArrayList<T> list = new ArrayList<T>();
		
		// look for factory methods
        Logg.info("Checking for Factory Methods");
        for (Method method : cl.getDeclaredMethods()) {
            if (isFactory(method)) {

                Logg.info("Found potential factory method " + method.getName());
               
                // if an exception is caught here, the next factory will be tried
                try {

            	    list.add(createInstance(cl, method));
                } catch (IllegalAccessException e) {
                	// DO Nothing
                } catch (InvocationTargetException e) {
                	// Do Nothing
                }
            }
        }
        
        return list;
	}

	/**
	 * Determine whether the supplied method is a factory method
	 * @param method
	 * @return
	 */
	private static boolean isFactory(Method method) {
		
		if (method.getReturnType() == method.getDeclaringClass()) {
			
			// check to see if the method takes an instance of itself as a parameter
			for (Class<?> clz : method.getParameterTypes()) {
	
	            if (clz == method.getDeclaringClass()) {
	                return false;
	            }
	        }
			
			// check if the method is clone // NOTE toString should only apply to strings, removed  
			if (method.getName() == "clone") {
				return false;
			}
			
			return true;
		}
		
		return false;
	}

	/**
	 * Create a fuzzy instance of the supplied class with the supplied factory method
	 * @param cl the class to create a fuzzy instance from
	 * @param factory the factory method with which to construct the fuzzy instance
	 * @return an instance of cl with pseudo-random parameters
	 * @throws IllegalAccessException
	 * @throws java.lang.reflect.InvocationTargetException
	 */
	@SuppressWarnings("unchecked")
	private static<T> T createInstance(Class<T> cl, Method factory) throws IllegalAccessException, InvocationTargetException {
	
	    Logg.info("Trying factory method " + factory.getName());
	
	    // come up with reasonable parameters
	    ArrayList<Object> params = new ArrayList<Object>();
	
	    for(TypeVariable<Method> param : factory.getTypeParameters()) {
	        params.add(Instantiator.createUnsetFuzzyInstance(param.getClass()));
	    }
	
	    try {
	        return (T)factory.invoke(null, params);
	    } catch (IllegalAccessException e) {
	        Logg.error("Access Denied");
	        throw e;
	    } catch (InvocationTargetException e) {
	        Logg.error("Target invocation failed;  Exception is as follows: \n" + e.getMessage());
	        throw e;
	    }
	}
}
